﻿using IdentityServer4.EntityFramework.Entities;
using Microsoft.EntityFrameworkCore;

namespace Devonline.Identity.Admin;

public class ApiScopeService
{
    private readonly ILogger<ApiScopeService> _logger;
    private readonly ConfigurationDbContext _context;
    private readonly HttpContext _httpContext;
    private readonly HttpRequest _request;
    public ApiScopeService(
        ILogger<ApiScopeService> logger,
        ConfigurationDbContext context,
        IHttpContextAccessor httpContextAccessor
        )
    {
        if (httpContextAccessor.HttpContext == null)
        {
            throw new ArgumentNullException(nameof(httpContextAccessor.HttpContext), "HttpContext can't be null!");
        }

        _logger = logger;
        _context = context;
        _httpContext = httpContextAccessor.HttpContext;
        _request = _httpContext.Request;
    }

    public virtual Task<ApiScopeProperty> GetApiScopePropertyAsync(int apiScopePropertyId)
    {
        return _context.ApiScopeProperties
            .Include(x => x.Scope)
            .Where(x => x.Id == apiScopePropertyId)
            .SingleOrDefaultAsync();
    }

    public virtual async Task<int> AddApiScopePropertyAsync(int apiScopeId, ApiScopeProperty apiScopeProperty)
    {
        var apiScope = await _context.ApiScopes.Where(x => x.Id == apiScopeId).SingleOrDefaultAsync();

        apiScopeProperty.Scope = apiScope;
        await _context.ApiScopeProperties.AddAsync(apiScopeProperty);

        return await _context.SaveChangesAsync();
    }

    public virtual async Task<int> DeleteApiScopePropertyAsync(ApiScopeProperty apiScopeProperty)
    {
        var propertyToDelete = await _context.ApiScopeProperties.Where(x => x.Id == apiScopeProperty.Id).SingleOrDefaultAsync();

        _context.ApiScopeProperties.Remove(propertyToDelete);

        return await _context.SaveChangesAsync();
    }

    public virtual async Task<bool> CanInsertApiScopePropertyAsync(ApiScopeProperty apiScopeProperty)
    {
        var existsWithSameName = await _context.ApiScopeProperties.Where(x => x.Key == apiScopeProperty.Key
                                                                               && x.Scope.Id == apiScopeProperty.Scope.Id).SingleOrDefaultAsync();
        return existsWithSameName == null;
    }


    public virtual async Task<string> GetApiScopeNameAsync(int apiScopeId)
    {
        var apiScopeName = await _context.ApiScopes.Where(x => x.Id == apiScopeId).Select(x => x.Name).SingleOrDefaultAsync();

        return apiScopeName;
    }

    public virtual async Task<PagedResult<ApiScopeProperty>> GetApiScopePropertiesAsync(int apiScopeId) => await _context.ApiScopeProperties.Where(x => x.Scope.Id == apiScopeId).PageByAsync(_request.GetPagedRequest());

    public virtual async Task<bool> CanInsertApiScopeAsync(ApiScope apiScope)
    {
        if (apiScope.Id == 0)
        {
            var existsWithSameName = await _context.ApiScopes.Where(x => x.Name == apiScope.Name).SingleOrDefaultAsync();
            return existsWithSameName == null;
        }
        else
        {
            var existsWithSameName = await _context.ApiScopes.Where(x => x.Name == apiScope.Name && x.Id != apiScope.Id).SingleOrDefaultAsync();
            return existsWithSameName == null;
        }
    }

    public virtual async Task<PagedResult<ApiScope>> GetApiScopesAsync(string search) => await _context.ApiScopes.WhereIf(!string.IsNullOrEmpty(search), x => x.Name.Contains(search)).PageByAsync(_request.GetPagedRequest());

    public virtual async Task<ICollection<string>> GetApiScopesNameAsync(string scope) => await _context.ApiScopes.WhereIf(!string.IsNullOrEmpty(scope), x => x.Name.Contains(scope)).Select(x => x.Name).ToListAsync();

    public virtual Task<ApiScope> GetApiScopeAsync(int apiScopeId)
    {
        return _context.ApiScopes
            .Include(x => x.UserClaims)
            .Include(x => x.Properties)
            .Where(x => x.Id == apiScopeId)
            .AsNoTracking()
            .SingleOrDefaultAsync();
    }

    /// <summary>
    /// Add new api scope
    /// </summary>
    /// <param name="apiScope"></param>
    /// <returns>This method return new api scope id</returns>
    public virtual async Task<int> AddApiScopeAsync(ApiScope apiScope)
    {
        await _context.ApiScopes.AddAsync(apiScope);

        await _context.SaveChangesAsync();

        return apiScope.Id;
    }

    private async Task RemoveApiScopeClaimsAsync(ApiScope apiScope)
    {
        //Remove old api scope claims
        var apiScopeClaims = await _context.ApiScopeClaims.Where(x => x.Scope.Id == apiScope.Id).ToListAsync();
        _context.ApiScopeClaims.RemoveRange(apiScopeClaims);
    }

    public virtual async Task<int> UpdateApiScopeAsync(ApiScope apiScope)
    {
        //Remove old relations
        await RemoveApiScopeClaimsAsync(apiScope);

        //Update with new data
        _context.ApiScopes.Update(apiScope);

        return await _context.SaveChangesAsync();
    }

    public virtual async Task<int> DeleteApiScopeAsync(ApiScope apiScope)
    {
        var apiScopeToDelete = await _context.ApiScopes.Where(x => x.Id == apiScope.Id).SingleOrDefaultAsync();
        _context.ApiScopes.Remove(apiScopeToDelete);

        return await _context.SaveChangesAsync();
    }

    public virtual async Task<int> SaveAllChangesAsync()
    {
        return await _context.SaveChangesAsync();
    }
}